(function (window, undefined) {
    'use strict';

    // ========================================================================
    // Initialise

    // Localise Globals
    var
        console = window.console || undefined, // Prevent a JSLint complain
        document = window.document, // Make sure we are using the correct document
        navigator = window.navigator, // Make sure we are using the correct navigator
        sessionStorage = false, // sessionStorage
        setTimeout = window.setTimeout,
        clearTimeout = window.clearTimeout,
        setInterval = window.setInterval,
        clearInterval = window.clearInterval,
        JSON = window.JSON,
        alert = window.alert,
        History = window.History = window.History || {}, // Public History Object
        history = window.history; // Old History Object

    try {
        sessionStorage = window.sessionStorage; // This will throw an exception in some browsers when cookies/localStorage are explicitly disabled (i.e. Chrome)
        sessionStorage.setItem('TEST', '1');
        sessionStorage.removeItem('TEST');
    } catch (e) {
        sessionStorage = false;
    }

    // MooTools Compatibility
    JSON.stringify = JSON.stringify || JSON.encode;
    JSON.parse = JSON.parse || JSON.decode;

    // Check Existence
    if ( typeof History.init !== 'undefined' ) {
        throw new Error('History.js Core has already been loaded...');
    }

    // Initialise History
    History.init = function () {
        // Check Load Status of Adapter
        if ( typeof History.Adapter === 'undefined' ) {
            return false;
        }

        // Check Load Status of Core
        if ( typeof History.initCore !== 'undefined' ) {
            History.initCore();
        }

        // Check Load Status of HTML4 Support
        if ( typeof History.initHtml4 !== 'undefined' ) {
            History.initHtml4();
        }

        // Return true
        return true;
    };


    // ========================================================================
    // Initialise Core

    // Initialise Core
    History.initCore = function () {
        // Initialise
        if ( typeof History.initCore.initialized !== 'undefined' ) {
            // Already Loaded
            return false;
        }
        else {
            History.initCore.initialized = true;
        }


        // ====================================================================
        // Options

        /**
		 * History.options
		 * Configurable options
		 */
        History.options = History.options || {};

        /**
		 * History.options.hashChangeInterval
		 * How long should the interval be before hashchange checks
		 */
        History.options.hashChangeInterval = History.options.hashChangeInterval || 100;

        /**
		 * History.options.safariPollInterval
		 * How long should the interval be before safari poll checks
		 */
        History.options.safariPollInterval = History.options.safariPollInterval || 500;

        /**
		 * History.options.doubleCheckInterval
		 * How long should the interval be before we perform a double check
		 */
        History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;

        /**
		 * History.options.disableSuid
		 * Force History not to append suid
		 */
        History.options.disableSuid = History.options.disableSuid || false;

        /**
		 * History.options.storeInterval
		 * How long should we wait between store calls
		 */
        History.options.storeInterval = History.options.storeInterval || 1000;

        /**
		 * History.options.busyDelay
		 * How long should we wait between busy events
		 */
        History.options.busyDelay = History.options.busyDelay || 250;

        /**
		 * History.options.debug
		 * If true will enable debug messages to be logged
		 */
        History.options.debug = History.options.debug || false;

        /**
		 * History.options.initialTitle
		 * What is the title of the initial state
		 */
        History.options.initialTitle = History.options.initialTitle || document.title;

        /**
		 * History.options.html4Mode
		 * If true, will force HTMl4 mode (hashtags)
		 */
        History.options.html4Mode = History.options.html4Mode || false;

        /**
		 * History.options.delayInit
		 * Want to override default options and call init manually.
		 */
        History.options.delayInit = History.options.delayInit || false;


        // ====================================================================
        // Interval record

        /**
		 * History.intervalList
		 * List of intervals set, to be cleared when document is unloaded.
		 */
        History.intervalList = [];

        /**
		 * History.clearAllIntervals
		 * Clears all setInterval instances.
		 */
        History.clearAllIntervals = function () {
            var i, il = History.intervalList;
            if (typeof il !== 'undefined' && il !== null) {
                for (i = 0; i < il.length; i++) {
                    clearInterval(il[i]);
                }
                History.intervalList = null;
            }
        };


        // ====================================================================
        // Debug

        /**
		 * History.debug(message,...)
		 * Logs the passed arguments if debug enabled
		 */
        History.debug = function () {
            if ( (History.options.debug || false) ) {
                History.log.apply(History, arguments);
            }
        };

        /**
		 * History.log(message,...)
		 * Logs the passed arguments
		 */
        History.log = function () {
            // Prepare
            var
                consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
                textarea = document.getElementById('log'),
                message,
                i, n,
                args, arg
				;

            // Write to Console
            if ( consoleExists ) {
                args = Array.prototype.slice.call(arguments);
                message = args.shift();
                if ( typeof console.debug !== 'undefined' ) {
                    console.debug.apply(console, [message, args]);
                }
                else {
                    console.log.apply(console, [message, args]);
                }
            }
            else {
                message = ('\n' + arguments[0] + '\n');
            }

            // Write to log
            for ( i = 1, n = arguments.length; i < n; ++i ) {
                arg = arguments[i];
                if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
                    try {
                        arg = JSON.stringify(arg);
                    }
                    catch ( Exception ) {
                        // Recursive Object
                    }
                }
                message += '\n' + arg + '\n';
            }

            // Textarea
            if ( textarea ) {
                textarea.value += message + '\n-----\n';
                textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
            }
            // No Textarea, No Console
            else if ( !consoleExists ) {
                alert(message);
            }

            // Return true
            return true;
        };


        // ====================================================================
        // Emulated Status

        /**
		 * History.getInternetExplorerMajorVersion()
		 * Get's the major version of Internet Explorer
		 * @return {integer}
		 * @license Public Domain
		 * @author Benjamin Arthur Lupton <contact@balupton.com>
		 * @author James Padolsey <https://gist.github.com/527683>
		 */
        History.getInternetExplorerMajorVersion = function () {
            var result = History.getInternetExplorerMajorVersion.cached =
					(typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
					    ?	History.getInternetExplorerMajorVersion.cached
					    :	(function () {
					        var v = 3,
					            div = document.createElement('div'),
					            all = div.getElementsByTagName('i');
					        while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
					        return (v > 4) ? v : false;
					    })()
				;
            return result;
        };

        /**
		 * History.isInternetExplorer()
		 * Are we using Internet Explorer?
		 * @return {boolean}
		 * @license Public Domain
		 * @author Benjamin Arthur Lupton <contact@balupton.com>
		 */
        History.isInternetExplorer = function () {
            var result =
				History.isInternetExplorer.cached =
				(typeof History.isInternetExplorer.cached !== 'undefined')
				    ?	History.isInternetExplorer.cached
				    :	Boolean(History.getInternetExplorerMajorVersion())
				;
            return result;
        };

        /**
		 * History.emulated
		 * Which features require emulating?
		 */

        if (History.options.html4Mode) {
            History.emulated = {
                pushState: true,
                hashChange: true
            };
        }

        else {

            History.emulated = {
                pushState: !Boolean(
                    window.history && window.history.pushState && window.history.replaceState
					&& !(
					    (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
						|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
					)
                ),
                hashChange: Boolean(
                    !(('onhashchange' in window) || ('onhashchange' in document))
					||
					(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
                )
            };
        }

        /**
		 * History.enabled
		 * Is History enabled?
		 */
        History.enabled = !History.emulated.pushState;

        /**
		 * History.bugs
		 * Which bugs are present
		 */
        History.bugs = {
            /**
			 * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
			 * https://bugs.webkit.org/show_bug.cgi?id=56249
			 */
            setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),

            /**
			 * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
			 * https://bugs.webkit.org/show_bug.cgi?id=42940
			 */
            safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),

            /**
			 * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
			 */
            ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),

            /**
			 * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
			 */
            hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
        };

        /**
		 * History.isEmptyObject(obj)
		 * Checks to see if the Object is Empty
		 * @param {Object} obj
		 * @return {boolean}
		 */
        History.isEmptyObject = function (obj) {
            for ( var name in obj ) {
                if ( obj.hasOwnProperty(name) ) {
                    return false;
                }
            }
            return true;
        };

        /**
		 * History.cloneObject(obj)
		 * Clones a object and eliminate all references to the original contexts
		 * @param {Object} obj
		 * @return {Object}
		 */
        History.cloneObject = function (obj) {
            var hash, newObj;
            if ( obj ) {
                hash = JSON.stringify(obj);
                newObj = JSON.parse(hash);
            }
            else {
                newObj = {};
            }
            return newObj;
        };


        // ====================================================================
        // URL Helpers

        /**
		 * History.getRootUrl()
		 * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
		 * @return {String} rootUrl
		 */
        History.getRootUrl = function () {
            // Create
            var rootUrl = document.location.protocol + '//' + (document.location.hostname || document.location.host);
            if ( document.location.port || false ) {
                rootUrl += ':' + document.location.port;
            }
            rootUrl += '/';

            // Return
            return rootUrl;
        };

        /**
		 * History.getBaseHref()
		 * Fetches the `href` attribute of the `<base href="...">` element if it exists
		 * @return {String} baseHref
		 */
        History.getBaseHref = function () {
            // Create
            var
                baseElements = document.getElementsByTagName('base'),
                baseElement = null,
                baseHref = '';

            // Test for Base Element
            if ( baseElements.length === 1 ) {
                // Prepare for Base Element
                baseElement = baseElements[0];
                baseHref = baseElement.href.replace(/[^\/]+$/, '');
            }

            // Adjust trailing slash
            baseHref = baseHref.replace(/\/+$/, '');
            if ( baseHref ) baseHref += '/';

            // Return
            return baseHref;
        };

        /**
		 * History.getBaseUrl()
		 * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
		 * @return {String} baseUrl
		 */
        History.getBaseUrl = function () {
            // Create
            var baseUrl = History.getBaseHref() || History.getBasePageUrl() || History.getRootUrl();

            // Return
            return baseUrl;
        };

        /**
		 * History.getPageUrl()
		 * Fetches the URL of the current page
		 * @return {String} pageUrl
		 */
        History.getPageUrl = function () {
            // Fetch
            var
                State = History.getState(false, false),
                stateUrl = (State || {}).url || History.getLocationHref(),
                pageUrl;

            // Create
            pageUrl = stateUrl.replace(/\/+$/, '').replace(/[^\/]+$/, function (part) {
                return (/\./).test(part) ? part : part + '/';
            });

            // Return
            return pageUrl;
        };

        /**
		 * History.getBasePageUrl()
		 * Fetches the Url of the directory of the current page
		 * @return {String} basePageUrl
		 */
        History.getBasePageUrl = function () {
            // Create
            var basePageUrl = (History.getLocationHref()).replace(/[#\?].*/, '').replace(/[^\/]+$/, function (part) {
                return (/[^\/]$/).test(part) ? '' : part;
            }).replace(/\/+$/, '') + '/';

            // Return
            return basePageUrl;
        };

        /**
		 * History.getFullUrl(url)
		 * Ensures that we have an absolute URL and not a relative URL
		 * @param {string} url
		 * @param {Boolean} allowBaseHref
		 * @return {string} fullUrl
		 */
        History.getFullUrl = function (url, allowBaseHref) {
            // Prepare
            var fullUrl = url, firstChar = url.substring(0, 1);
            allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;

            // Check
            if ( /[a-z]+\:\/\//.test(url) ) {
                // Full URL
            }
            else if ( firstChar === '/' ) {
                // Root URL
                fullUrl = History.getRootUrl() + url.replace(/^\/+/, '');
            }
            else if ( firstChar === '#' ) {
                // Anchor URL
                fullUrl = History.getPageUrl().replace(/#.*/, '') + url;
            }
            else if ( firstChar === '?' ) {
                // Query URL
                fullUrl = History.getPageUrl().replace(/[\?#].*/, '') + url;
            }
            else {
                // Relative URL
                if ( allowBaseHref ) {
                    fullUrl = History.getBaseUrl() + url.replace(/^(\.\/)+/, '');
                } else {
                    fullUrl = History.getBasePageUrl() + url.replace(/^(\.\/)+/, '');
                }
                // We have an if condition above as we do not want hashes
                // which are relative to the baseHref in our URLs
                // as if the baseHref changes, then all our bookmarks
                // would now point to different locations
                // whereas the basePageUrl will always stay the same
            }

            // Return
            return fullUrl.replace(/\#$/, '');
        };

        /**
		 * History.getShortUrl(url)
		 * Ensures that we have a relative URL and not a absolute URL
		 * @param {string} url
		 * @return {string} url
		 */
        History.getShortUrl = function (url) {
            // Prepare
            var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();

            // Trim baseUrl
            if ( History.emulated.pushState ) {
                // We are in a if statement as when pushState is not emulated
                // The actual url these short urls are relative to can change
                // So within the same session, we the url may end up somewhere different
                shortUrl = shortUrl.replace(baseUrl, '');
            }

            // Trim rootUrl
            shortUrl = shortUrl.replace(rootUrl, '/');

            // Ensure we can still detect it as a state
            if ( History.isTraditionalAnchor(shortUrl) ) {
                shortUrl = './' + shortUrl;
            }

            // Clean It
            shortUrl = shortUrl.replace(/^(\.\/)+/g, './').replace(/\#$/, '');

            // Return
            return shortUrl;
        };

        /**
		 * History.getLocationHref(document)
		 * Returns a normalized version of document.location.href
		 * accounting for browser inconsistencies, etc.
		 *
		 * This URL will be URI-encoded and will include the hash
		 *
		 * @param {object} document
		 * @return {string} url
		 */
        History.getLocationHref = function (doc) {
            doc = doc || document;

            // most of the time, this will be true
            if (doc.URL === doc.location.href)
                return doc.location.href;

            // some versions of webkit URI-decode document.location.href
            // but they leave document.URL in an encoded state
            if (doc.location.href === decodeURIComponent(doc.URL))
                return doc.URL;

            // FF 3.6 only updates document.URL when a page is reloaded
            // document.location.href is updated correctly
            if (doc.location.hash && decodeURIComponent(doc.location.href.replace(/^[^#]+/, '')) === doc.location.hash)
                return doc.location.href;

            if (doc.URL.indexOf('#') == -1 && doc.location.href.indexOf('#') != -1)
                return doc.location.href;
			
            return doc.URL || doc.location.href;
        };


        // ====================================================================
        // State Storage

        /**
		 * History.store
		 * The store for all session specific data
		 */
        History.store = {};

        /**
		 * History.idToState
		 * 1-1: State ID to State Object
		 */
        History.idToState = History.idToState || {};

        /**
		 * History.stateToId
		 * 1-1: State String to State ID
		 */
        History.stateToId = History.stateToId || {};

        /**
		 * History.urlToId
		 * 1-1: State URL to State ID
		 */
        History.urlToId = History.urlToId || {};

        /**
		 * History.storedStates
		 * Store the states in an array
		 */
        History.storedStates = History.storedStates || [];

        /**
		 * History.savedStates
		 * Saved the states in an array
		 */
        History.savedStates = History.savedStates || [];

        /**
		 * History.noramlizeStore()
		 * Noramlize the store by adding necessary values
		 */
        History.normalizeStore = function () {
            History.store.idToState = History.store.idToState || {};
            History.store.urlToId = History.store.urlToId || {};
            History.store.stateToId = History.store.stateToId || {};
        };

        /**
		 * History.getState()
		 * Get an object containing the data, title and url of the current state
		 * @param {Boolean} friendly
		 * @param {Boolean} create
		 * @return {Object} State
		 */
        History.getState = function (friendly, create) {
            // Prepare
            if ( typeof friendly === 'undefined' ) { friendly = true; }
            if ( typeof create === 'undefined' ) { create = true; }

            // Fetch
            var State = History.getLastSavedState();

            // Create
            if ( !State && create ) {
                State = History.createStateObject();
            }

            // Adjust
            if ( friendly ) {
                State = History.cloneObject(State);
                State.url = State.cleanUrl || State.url;
            }

            // Return
            return State;
        };

        /**
		 * History.getIdByState(State)
		 * Gets a ID for a State
		 * @param {State} newState
		 * @return {String} id
		 */
        History.getIdByState = function (newState) {

            // Fetch ID
            var id = History.extractId(newState.url),
                str;

            if ( !id ) {
                // Find ID via State String
                str = History.getStateString(newState);
                if ( typeof History.stateToId[str] !== 'undefined' ) {
                    id = History.stateToId[str];
                }
                else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
                    id = History.store.stateToId[str];
                }
                else {
                    // Generate a new ID
                    while ( true ) {
                        id = (new Date()).getTime() + String(Math.random()).replace(/\D/g, '');
                        if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
                            break;
                        }
                    }

                    // Apply the new State to the ID
                    History.stateToId[str] = id;
                    History.idToState[id] = newState;
                }
            }

            // Return ID
            return id;
        };

        /**
		 * History.normalizeState(State)
		 * Expands a State Object
		 * @param {object} State
		 * @return {object}
		 */
        History.normalizeState = function (oldState) {
            // Variables
            var newState, dataNotEmpty;

            // Prepare
            if ( !oldState || (typeof oldState !== 'object') ) {
                oldState = {};
            }

            // Check
            if ( typeof oldState.normalized !== 'undefined' ) {
                return oldState;
            }

            // Adjust
            if ( !oldState.data || (typeof oldState.data !== 'object') ) {
                oldState.data = {};
            }

            // ----------------------------------------------------------------

            // Create
            newState = {};
            newState.normalized = true;
            newState.title = oldState.title || '';
            newState.url = History.getFullUrl(oldState.url ? oldState.url : (History.getLocationHref()));
            newState.hash = History.getShortUrl(newState.url);
            newState.data = History.cloneObject(oldState.data);

            // Fetch ID
            newState.id = History.getIdByState(newState);

            // ----------------------------------------------------------------

            // Clean the URL
            newState.cleanUrl = newState.url.replace(/\??\&_suid.*/, '');
            newState.url = newState.cleanUrl;

            // Check to see if we have more than just a url
            dataNotEmpty = !History.isEmptyObject(newState.data);

            // Apply
            if ( (newState.title || dataNotEmpty) && History.options.disableSuid !== true ) {
                // Add ID to Hash
                newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/, '');
                if ( !/\?/.test(newState.hash) ) {
                    newState.hash += '?';
                }
                newState.hash += '&_suid=' + newState.id;
            }

            // Create the Hashed URL
            newState.hashedUrl = History.getFullUrl(newState.hash);

            // ----------------------------------------------------------------

            // Update the URL if we have a duplicate
            if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
                newState.url = newState.hashedUrl;
            }

            // ----------------------------------------------------------------

            // Return
            return newState;
        };

        /**
		 * History.createStateObject(data,title,url)
		 * Creates a object based on the data, title and url state params
		 * @param {object} data
		 * @param {string} title
		 * @param {string} url
		 * @return {object}
		 */
        History.createStateObject = function (data, title, url) {
            // Hashify
            var State = {
                'data': data,
                'title': title,
                'url': url
            };

            // Expand the State
            State = History.normalizeState(State);

            // Return object
            return State;
        };

        /**
		 * History.getStateById(id)
		 * Get a state by it's UID
		 * @param {String} id
		 */
        History.getStateById = function (id) {
            // Prepare
            id = String(id);

            // Retrieve
            var State = History.idToState[id] || History.store.idToState[id] || undefined;

            // Return State
            return State;
        };

        /**
		 * Get a State's String
		 * @param {State} passedState
		 */
        History.getStateString = function (passedState) {
            // Prepare
            var State, cleanedState, str;

            // Fetch
            State = History.normalizeState(passedState);

            // Clean
            cleanedState = {
                data: State.data,
                title: passedState.title,
                url: passedState.url
            };

            // Fetch
            str = JSON.stringify(cleanedState);

            // Return
            return str;
        };

        /**
		 * Get a State's ID
		 * @param {State} passedState
		 * @return {String} id
		 */
        History.getStateId = function (passedState) {
            // Prepare
            var State, id;

            // Fetch
            State = History.normalizeState(passedState);

            // Fetch
            id = State.id;

            // Return
            return id;
        };

        /**
		 * History.getHashByState(State)
		 * Creates a Hash for the State Object
		 * @param {State} passedState
		 * @return {String} hash
		 */
        History.getHashByState = function (passedState) {
            // Prepare
            var State, hash;

            // Fetch
            State = History.normalizeState(passedState);

            // Hash
            hash = State.hash;

            // Return
            return hash;
        };

        /**
		 * History.extractId(url_or_hash)
		 * Get a State ID by it's URL or Hash
		 * @param {string} url_or_hash
		 * @return {string} id
		 */
        History.extractId = function ( url_or_hash ) {
            // Prepare
            // var id, parts, url, tmp;
            var id, parts, tmp;

            // Extract
			
            // If the URL has a #, use the id from before the #
            if (url_or_hash.indexOf('#') != -1)
            {
                tmp = url_or_hash.split('#')[0];
            }
            else
            {
                tmp = url_or_hash;
            }
			
            parts = /(.*)\&_suid=([0-9]+)$/.exec(tmp);
            // url = parts ? (parts[1] || url_or_hash) : url_or_hash;
            id = parts ? String(parts[2] || '') : '';

            // Return
            return id || false;
        };

        /**
		 * History.isTraditionalAnchor
		 * Checks to see if the url is a traditional anchor or not
		 * @param {String} url_or_hash
		 * @return {Boolean}
		 */
        History.isTraditionalAnchor = function (url_or_hash) {
            // Check
            var isTraditional = !(/[\/\?\.]/.test(url_or_hash));

            // Return
            return isTraditional;
        };

        /**
		 * History.extractState
		 * Get a State by it's URL or Hash
		 * @param {String} url_or_hash
		 * @return {State|null}
		 */
        History.extractState = function (url_or_hash, create) {
            // Prepare
            var State = null, id, url;
            create = create || false;

            // Fetch SUID
            id = History.extractId(url_or_hash);
            if ( id ) {
                State = History.getStateById(id);
            }

            // Fetch SUID returned no State
            if ( !State ) {
                // Fetch URL
                url = History.getFullUrl(url_or_hash);

                // Check URL
                id = History.getIdByUrl(url) || false;
                if ( id ) {
                    State = History.getStateById(id);
                }

                // Create State
                if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
                    State = History.createStateObject(null, null, url);
                }
            }

            // Return
            return State;
        };

        /**
		 * History.getIdByUrl()
		 * Get a State ID by a State URL
		 */
        History.getIdByUrl = function (url) {
            // Fetch
            var id = History.urlToId[url] || History.store.urlToId[url] || undefined;

            // Return
            return id;
        };

        /**
		 * History.getLastSavedState()
		 * Get an object containing the data, title and url of the current state
		 * @return {Object} State
		 */
        History.getLastSavedState = function () {
            return History.savedStates[History.savedStates.length - 1] || undefined;
        };

        /**
		 * History.getLastStoredState()
		 * Get an object containing the data, title and url of the current state
		 * @return {Object} State
		 */
        History.getLastStoredState = function () {
            return History.storedStates[History.storedStates.length - 1] || undefined;
        };

        /**
		 * History.hasUrlDuplicate
		 * Checks if a Url will have a url conflict
		 * @param {Object} newState
		 * @return {Boolean} hasDuplicate
		 */
        History.hasUrlDuplicate = function (newState) {
            // Prepare
            var hasDuplicate = false,
                oldState;

            // Fetch
            oldState = History.extractState(newState.url);

            // Check
            hasDuplicate = oldState && oldState.id !== newState.id;

            // Return
            return hasDuplicate;
        };

        /**
		 * History.storeState
		 * Store a State
		 * @param {Object} newState
		 * @return {Object} newState
		 */
        History.storeState = function (newState) {
            // Store the State
            History.urlToId[newState.url] = newState.id;

            // Push the State
            History.storedStates.push(History.cloneObject(newState));

            // Return newState
            return newState;
        };

        /**
		 * History.isLastSavedState(newState)
		 * Tests to see if the state is the last state
		 * @param {Object} newState
		 * @return {boolean} isLast
		 */
        History.isLastSavedState = function (newState) {
            // Prepare
            var isLast = false,
                newId, oldState, oldId;

            // Check
            if ( History.savedStates.length ) {
                newId = newState.id;
                oldState = History.getLastSavedState();
                oldId = oldState.id;

                // Check
                isLast = (newId === oldId);
            }

            // Return
            return isLast;
        };

        /**
		 * History.saveState
		 * Push a State
		 * @param {Object} newState
		 * @return {boolean} changed
		 */
        History.saveState = function (newState) {
            // Check Hash
            if ( History.isLastSavedState(newState) ) {
                return false;
            }

            // Push the State
            History.savedStates.push(History.cloneObject(newState));

            // Return true
            return true;
        };

        /**
		 * History.getStateByIndex()
		 * Gets a state by the index
		 * @param {integer} index
		 * @return {Object}
		 */
        History.getStateByIndex = function (index) {
            // Prepare
            var State = null;

            // Handle
            if ( typeof index === 'undefined' ) {
                // Get the last inserted
                State = History.savedStates[History.savedStates.length - 1];
            }
            else if ( index < 0 ) {
                // Get from the end
                State = History.savedStates[History.savedStates.length + index];
            }
            else {
                // Get from the beginning
                State = History.savedStates[index];
            }

            // Return State
            return State;
        };
		
        /**
		 * History.getCurrentIndex()
		 * Gets the current index
		 * @return (integer)
		*/
        History.getCurrentIndex = function () {
            // Prepare
            var index = null;
			
            // No states saved
            if (History.savedStates.length < 1) {
                index = 0;
            }
            else {
                index = History.savedStates.length - 1;
            }
            return index;
        };

        // ====================================================================
        // Hash Helpers

        /**
		 * History.getHash()
		 * @param {Location=} location
		 * Gets the current document hash
		 * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers
		 * @return {string}
		 */
        History.getHash = function (doc) {
            var url = History.getLocationHref(doc),
                hash;
            hash = History.getHashByUrl(url);
            return hash;
        };

        /**
		 * History.unescapeHash()
		 * normalize and Unescape a Hash
		 * @param {String} hash
		 * @return {string}
		 */
        History.unescapeHash = function (hash) {
            // Prepare
            var result = History.normalizeHash(hash);

            // Unescape hash
            result = decodeURIComponent(result);

            // Return result
            return result;
        };

        /**
		 * History.normalizeHash()
		 * normalize a hash across browsers
		 * @return {string}
		 */
        History.normalizeHash = function (hash) {
            // Prepare
            var result = hash.replace(/[^#]*#/, '').replace(/#.*/, '');

            // Return result
            return result;
        };

        /**
		 * History.setHash(hash)
		 * Sets the document hash
		 * @param {string} hash
		 * @return {History}
		 */
        History.setHash = function (hash, queue) {
            // Prepare
            var State, pageUrl;

            // Handle Queueing
            if ( queue !== false && History.busy() ) {
                // Wait + Push to Queue
                // History.debug('History.setHash: we must wait', arguments);
                History.pushQueue({
                    scope: History,
                    callback: History.setHash,
                    args: arguments,
                    queue: queue
                });
                return false;
            }

            // Log
            // History.debug('History.setHash: called',hash);

            // Make Busy + Continue
            History.busy(true);

            // Check if hash is a state
            State = History.extractState(hash, true);
            if ( State && !History.emulated.pushState ) {
                // Hash is a state so skip the setHash
                // History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);

                // PushState
                History.pushState(State.data, State.title, State.url, false);
            }
            else if ( History.getHash() !== hash ) {
                // Hash is a proper hash, so apply it

                // Handle browser bugs
                if ( History.bugs.setHash ) {
                    // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249

                    // Fetch the base page
                    pageUrl = History.getPageUrl();

                    // Safari hash apply
                    History.pushState(null, null, pageUrl + '#' + hash, false);
                }
                else {
                    // Normal hash apply
                    document.location.hash = hash;
                }
            }

            // Chain
            return History;
        };

        /**
		 * History.escape()
		 * normalize and Escape a Hash
		 * @return {string}
		 */
        History.escapeHash = function (hash) {
            // Prepare
            var result = History.normalizeHash(hash);

            // Escape hash
            result = window.encodeURIComponent(result);

            // IE6 Escape Bug
            if ( !History.bugs.hashEscape ) {
                // Restore common parts
                result = result
                    .replace(/\%21/g, '!')
                    .replace(/\%26/g, '&')
                    .replace(/\%3D/g, '=')
                    .replace(/\%3F/g, '?');
            }

            // Return result
            return result;
        };

        /**
		 * History.getHashByUrl(url)
		 * Extracts the Hash from a URL
		 * @param {string} url
		 * @return {string} url
		 */
        History.getHashByUrl = function (url) {
            // Extract the hash
            var hash = String(url)
                .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
				;

            // Unescape hash
            hash = History.unescapeHash(hash);

            // Return hash
            return hash;
        };

        /**
		 * History.setTitle(title)
		 * Applies the title to the document
		 * @param {State} newState
		 * @return {Boolean}
		 */
        History.setTitle = function (newState) {
            // Prepare
            var title = newState.title,
                firstState;

            // Initial
            if ( !title ) {
                firstState = History.getStateByIndex(0);
                if ( firstState && firstState.url === newState.url ) {
                    title = firstState.title || History.options.initialTitle;
                }
            }

            // Apply
            try {
                document.getElementsByTagName('title')[0].innerHTML = title.replace('<', '&lt;').replace('>', '&gt;').replace(' & ', ' &amp; ');
            }
            catch ( Exception ) { }
            document.title = title;

            // Chain
            return History;
        };


        // ====================================================================
        // Queueing

        /**
		 * History.queues
		 * The list of queues to use
		 * First In, First Out
		 */
        History.queues = [];

        /**
		 * History.busy(value)
		 * @param {boolean} value [optional]
		 * @return {boolean} busy
		 */
        History.busy = function (value) {
            // Apply
            if ( typeof value !== 'undefined' ) {
                // History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
                History.busy.flag = value;
            }
            // Default
            else if ( typeof History.busy.flag === 'undefined' ) {
                History.busy.flag = false;
            }

            // Queue
            if ( !History.busy.flag ) {
                // Execute the next item in the queue
                clearTimeout(History.busy.timeout);
                var fireNext = function () {
                    var i, queue, item;
                    if ( History.busy.flag ) return;
                    for ( i = History.queues.length - 1; i >= 0; --i ) {
                        queue = History.queues[i];
                        if ( queue.length === 0 ) continue;
                        item = queue.shift();
                        History.fireQueueItem(item);
                        History.busy.timeout = setTimeout(fireNext, History.options.busyDelay);
                    }
                };
                History.busy.timeout = setTimeout(fireNext, History.options.busyDelay);
            }

            // Return
            return History.busy.flag;
        };

        /**
		 * History.busy.flag
		 */
        History.busy.flag = false;

        /**
		 * History.fireQueueItem(item)
		 * Fire a Queue Item
		 * @param {Object} item
		 * @return {Mixed} result
		 */
        History.fireQueueItem = function (item) {
            return item.callback.apply(item.scope || History, item.args || []);
        };

        /**
		 * History.pushQueue(callback,args)
		 * Add an item to the queue
		 * @param {Object} item [scope,callback,args,queue]
		 */
        History.pushQueue = function (item) {
            // Prepare the queue
            History.queues[item.queue || 0] = History.queues[item.queue || 0] || [];

            // Add to the queue
            History.queues[item.queue || 0].push(item);

            // Chain
            return History;
        };

        /**
		 * History.queue (item,queue), (func,queue), (func), (item)
		 * Either firs the item now if not busy, or adds it to the queue
		 */
        History.queue = function (item, queue) {
            // Prepare
            if ( typeof item === 'function' ) {
                item = {
                    callback: item
                };
            }
            if ( typeof queue !== 'undefined' ) {
                item.queue = queue;
            }

            // Handle
            if ( History.busy() ) {
                History.pushQueue(item);
            } else {
                History.fireQueueItem(item);
            }

            // Chain
            return History;
        };

        /**
		 * History.clearQueue()
		 * Clears the Queue
		 */
        History.clearQueue = function () {
            History.busy.flag = false;
            History.queues = [];
            return History;
        };


        // ====================================================================
        // IE Bug Fix

        /**
		 * History.stateChanged
		 * States whether or not the state has changed since the last double check was initialised
		 */
        History.stateChanged = false;

        /**
		 * History.doubleChecker
		 * Contains the timeout used for the double checks
		 */
        History.doubleChecker = false;

        /**
		 * History.doubleCheckComplete()
		 * Complete a double check
		 * @return {History}
		 */
        History.doubleCheckComplete = function () {
            // Update
            History.stateChanged = true;

            // Clear
            History.doubleCheckClear();

            // Chain
            return History;
        };

        /**
		 * History.doubleCheckClear()
		 * Clear a double check
		 * @return {History}
		 */
        History.doubleCheckClear = function () {
            // Clear
            if ( History.doubleChecker ) {
                clearTimeout(History.doubleChecker);
                History.doubleChecker = false;
            }

            // Chain
            return History;
        };

        /**
		 * History.doubleCheck()
		 * Create a double check
		 * @return {History}
		 */
        History.doubleCheck = function (tryAgain) {
            // Reset
            History.stateChanged = false;
            History.doubleCheckClear();

            // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
            // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
            if ( History.bugs.ieDoubleCheck ) {
                // Apply Check
                History.doubleChecker = setTimeout(
                    function () {
                        History.doubleCheckClear();
                        if ( !History.stateChanged ) {
                            // History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
                            // Re-Attempt
                            tryAgain();
                        }
                        return true;
                    },
                    History.options.doubleCheckInterval
                );
            }

            // Chain
            return History;
        };


        // ====================================================================
        // Safari Bug Fix

        /**
		 * History.safariStatePoll()
		 * Poll the current state
		 * @return {History}
		 */
        History.safariStatePoll = function () {
            // Poll the URL

            // Get the Last State which has the new URL
            var
                urlState = History.extractState(History.getLocationHref()),
                newState;

            // Check for a difference
            if ( !History.isLastSavedState(urlState) ) {
                newState = urlState;
            }
            else {
                return;
            }

            // Check if we have a state with that url
            // If not create it
            if ( !newState ) {
                // History.debug('History.safariStatePoll: new');
                newState = History.createStateObject();
            }

            // Apply the New State
            // History.debug('History.safariStatePoll: trigger');
            History.Adapter.trigger(window, 'popstate');

            // Chain
            return History;
        };


        // ====================================================================
        // State Aliases

        /**
		 * History.back(queue)
		 * Send the browser history back one item
		 * @param {Integer} queue [optional]
		 */
        History.back = function (queue) {
            // History.debug('History.back: called', arguments);

            // Handle Queueing
            if ( queue !== false && History.busy() ) {
                // Wait + Push to Queue
                // History.debug('History.back: we must wait', arguments);
                History.pushQueue({
                    scope: History,
                    callback: History.back,
                    args: arguments,
                    queue: queue
                });
                return false;
            }

            // Make Busy + Continue
            History.busy(true);

            // Fix certain browser bugs that prevent the state from changing
            History.doubleCheck(function () {
                History.back(false);
            });

            // Go back
            history.go(-1);

            // End back closure
            return true;
        };

        /**
		 * History.forward(queue)
		 * Send the browser history forward one item
		 * @param {Integer} queue [optional]
		 */
        History.forward = function (queue) {
            // History.debug('History.forward: called', arguments);

            // Handle Queueing
            if ( queue !== false && History.busy() ) {
                // Wait + Push to Queue
                // History.debug('History.forward: we must wait', arguments);
                History.pushQueue({
                    scope: History,
                    callback: History.forward,
                    args: arguments,
                    queue: queue
                });
                return false;
            }

            // Make Busy + Continue
            History.busy(true);

            // Fix certain browser bugs that prevent the state from changing
            History.doubleCheck(function () {
                History.forward(false);
            });

            // Go forward
            history.go(1);

            // End forward closure
            return true;
        };

        /**
		 * History.go(index,queue)
		 * Send the browser history back or forward index times
		 * @param {Integer} queue [optional]
		 */
        History.go = function (index, queue) {
            // History.debug('History.go: called', arguments);

            // Prepare
            var i;

            // Handle
            if ( index > 0 ) {
                // Forward
                for ( i = 1; i <= index; ++i ) {
                    History.forward(queue);
                }
            }
            else if ( index < 0 ) {
                // Backward
                for ( i = -1; i >= index; --i ) {
                    History.back(queue);
                }
            }
            else {
                throw new Error('History.go: History.go requires a positive or negative integer passed.');
            }

            // Chain
            return History;
        };


        // ====================================================================
        // HTML5 State Support

        // Non-Native pushState Implementation
        if ( History.emulated.pushState ) {
            /*
			 * Provide Skeleton for HTML4 Browsers
			 */

            // Prepare
            var emptyFunction = function () {};
            History.pushState = History.pushState || emptyFunction;
            History.replaceState = History.replaceState || emptyFunction;
        } // History.emulated.pushState

        // Native pushState Implementation
        else {
            /*
			 * Use native HTML5 History API Implementation
			 */

            /**
			 * History.onPopState(event,extra)
			 * Refresh the Current State
			 */
            History.onPopState = function (event, extra) {
                // Prepare
                var stateId = false, newState = false, currentHash, currentState;

                // Reset the double check
                History.doubleCheckComplete();

                // Check for a Hash, and handle apporiatly
                currentHash = History.getHash();
                if ( currentHash ) {
                    // Expand Hash
                    currentState = History.extractState(currentHash || History.getLocationHref(), true);
                    if ( currentState ) {
                        // We were able to parse it, it must be a State!
                        // Let's forward to replaceState
                        // History.debug('History.onPopState: state anchor', currentHash, currentState);
                        History.replaceState(currentState.data, currentState.title, currentState.url, false);
                    }
                    else {
                        // Traditional Anchor
                        // History.debug('History.onPopState: traditional anchor', currentHash);
                        History.Adapter.trigger(window, 'anchorchange');
                        History.busy(false);
                    }

                    // We don't care for hashes
                    History.expectedStateId = false;
                    return false;
                }

                // Ensure
                stateId = History.Adapter.extractEventData('state', event, extra) || false;

                // Fetch State
                if ( stateId ) {
                    // Vanilla: Back/forward button was used
                    newState = History.getStateById(stateId);
                }
                else if ( History.expectedStateId ) {
                    // Vanilla: A new state was pushed, and popstate was called manually
                    newState = History.getStateById(History.expectedStateId);
                }
                else {
                    // Initial State
                    newState = History.extractState(History.getLocationHref());
                }

                // The State did not exist in our store
                if ( !newState ) {
                    // Regenerate the State
                    newState = History.createStateObject(null, null, History.getLocationHref());
                }

                // Clean
                History.expectedStateId = false;

                // Check if we are the same state
                if ( History.isLastSavedState(newState) ) {
                    // There has been no change (just the page's hash has finally propagated)
                    // History.debug('History.onPopState: no change', newState, History.savedStates);
                    History.busy(false);
                    return false;
                }

                // Store the State
                History.storeState(newState);
                History.saveState(newState);

                // Force update of the title
                History.setTitle(newState);

                // Fire Our Event
                History.Adapter.trigger(window, 'statechange');
                History.busy(false);

                // Return true
                return true;
            };
            History.Adapter.bind(window, 'popstate', History.onPopState);

            /**
			 * History.pushState(data,title,url)
			 * Add a new State to the history object, become it, and trigger onpopstate
			 * We have to trigger for HTML4 compatibility
			 * @param {object} data
			 * @param {string} title
			 * @param {string} url
			 * @return {true}
			 */
            History.pushState = function (data, title, url, queue) {
                // History.debug('History.pushState: called', arguments);

                // Check the State
                if ( History.getHashByUrl(url) && History.emulated.pushState ) {
                    throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
                }

                // Handle Queueing
                if ( queue !== false && History.busy() ) {
                    // Wait + Push to Queue
                    // History.debug('History.pushState: we must wait', arguments);
                    History.pushQueue({
                        scope: History,
                        callback: History.pushState,
                        args: arguments,
                        queue: queue
                    });
                    return false;
                }

                // Make Busy + Continue
                History.busy(true);

                // Create the newState
                var newState = History.createStateObject(data, title, url);

                // Check it
                if ( History.isLastSavedState(newState) ) {
                    // Won't be a change
                    History.busy(false);
                }
                else {
                    // Store the newState
                    History.storeState(newState);
                    History.expectedStateId = newState.id;

                    // Push the newState
                    history.pushState(newState.id, newState.title, newState.url);

                    // Fire HTML5 Event
                    History.Adapter.trigger(window, 'popstate');
                }

                // End pushState closure
                return true;
            };

            /**
			 * History.replaceState(data,title,url)
			 * Replace the State and trigger onpopstate
			 * We have to trigger for HTML4 compatibility
			 * @param {object} data
			 * @param {string} title
			 * @param {string} url
			 * @return {true}
			 */
            History.replaceState = function (data, title, url, queue) {
                // History.debug('History.replaceState: called', arguments);

                // Check the State
                if ( History.getHashByUrl(url) && History.emulated.pushState ) {
                    throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
                }

                // Handle Queueing
                if ( queue !== false && History.busy() ) {
                    // Wait + Push to Queue
                    // History.debug('History.replaceState: we must wait', arguments);
                    History.pushQueue({
                        scope: History,
                        callback: History.replaceState,
                        args: arguments,
                        queue: queue
                    });
                    return false;
                }

                // Make Busy + Continue
                History.busy(true);

                // Create the newState
                var newState = History.createStateObject(data, title, url);

                // Check it
                if ( History.isLastSavedState(newState) ) {
                    // Won't be a change
                    History.busy(false);
                }
                else {
                    // Store the newState
                    History.storeState(newState);
                    History.expectedStateId = newState.id;

                    // Push the newState
                    history.replaceState(newState.id, newState.title, newState.url);

                    // Fire HTML5 Event
                    History.Adapter.trigger(window, 'popstate');
                }

                // End replaceState closure
                return true;
            };

        } // !History.emulated.pushState


        // ====================================================================
        // Initialise

        /**
		 * Load the Store
		 */
        if ( sessionStorage ) {
            // Fetch
            try {
                History.store = JSON.parse(sessionStorage.getItem('History.store')) || {};
            }
            catch ( err ) {
                History.store = {};
            }

            // Normalize
            History.normalizeStore();
        }
        else {
            // Default Load
            History.store = {};
            History.normalizeStore();
        }

        /**
		 * Clear Intervals on exit to prevent memory leaks
		 */
        History.Adapter.bind(window, 'unload', History.clearAllIntervals);

        /**
		 * Create the initial State
		 */
        History.saveState(History.storeState(History.extractState(History.getLocationHref(), true)));

        /**
		 * Bind for Saving Store
		 */
        if ( sessionStorage ) {
            // When the page is closed
            History.onUnload = function () {
                // Prepare
                var	currentStore, item, currentStoreString;

                // Fetch
                try {
                    currentStore = JSON.parse(sessionStorage.getItem('History.store')) || {};
                }
                catch ( err ) {
                    currentStore = {};
                }

                // Ensure
                currentStore.idToState = currentStore.idToState || {};
                currentStore.urlToId = currentStore.urlToId || {};
                currentStore.stateToId = currentStore.stateToId || {};

                // Sync
                for ( item in History.idToState ) {
                    if ( !History.idToState.hasOwnProperty(item) ) {
                        continue;
                    }
                    currentStore.idToState[item] = History.idToState[item];
                }
                for ( item in History.urlToId ) {
                    if ( !History.urlToId.hasOwnProperty(item) ) {
                        continue;
                    }
                    currentStore.urlToId[item] = History.urlToId[item];
                }
                for ( item in History.stateToId ) {
                    if ( !History.stateToId.hasOwnProperty(item) ) {
                        continue;
                    }
                    currentStore.stateToId[item] = History.stateToId[item];
                }

                // Update
                History.store = currentStore;
                History.normalizeStore();

                // In Safari, going into Private Browsing mode causes the
                // Session Storage object to still exist but if you try and use
                // or set any property/function of it it throws the exception
                // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to
                // add something to storage that exceeded the quota." infinitely
                // every second.
                currentStoreString = JSON.stringify(currentStore);
                try {
                    // Store
                    sessionStorage.setItem('History.store', currentStoreString);
                }
                catch (e) {
                    if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
                        if (sessionStorage.length) {
                            // Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply
                            // removing/resetting the storage can work.
                            sessionStorage.removeItem('History.store');
                            sessionStorage.setItem('History.store', currentStoreString);
                        } else {
                            // Otherwise, we're probably private browsing in Safari, so we'll ignore the exception.
                        }
                    } else {
                        throw e;
                    }
                }
            };

            // For Internet Explorer
            History.intervalList.push(setInterval(History.onUnload, History.options.storeInterval));

            // For Other Browsers
            History.Adapter.bind(window, 'beforeunload', History.onUnload);
            History.Adapter.bind(window, 'unload', History.onUnload);

            // Both are enabled for consistency
        }

        // Non-Native pushState Implementation
        if ( !History.emulated.pushState ) {
            // Be aware, the following is only for native pushState implementations
            // If you are wanting to include something for all browsers
            // Then include it above this if block

            /**
			 * Setup Safari Fix
			 */
            if ( History.bugs.safariPoll ) {
                History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
            }

            /**
			 * Ensure Cross Browser Compatibility
			 */
            if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName || '') === 'Mozilla' ) {
                /**
				 * Fix Safari HashChange Issue
				 */

                // Setup Alias
                History.Adapter.bind(window, 'hashchange', function () {
                    History.Adapter.trigger(window, 'popstate');
                });

                // Initialise Alias
                if ( History.getHash() ) {
                    History.Adapter.onDomLoad(function () {
                        History.Adapter.trigger(window, 'hashchange');
                    });
                }
            }

        } // !History.emulated.pushState


    }; // History.initCore

    // Try to Initialise History
    if (!History.options || !History.options.delayInit) {
        History.init();
    }

})(window);